package edu.pitt.cs.cs2510.project3.algorithm;

import java.util.ArrayList;
import java.util.Iterator;

import edu.pitt.cs.cs2510.project3.configuration.InputParameters;
import edu.pitt.cs.cs2510.project3.model.*;


public class LoadBalancer {
	InputParameters simParams = null;
	FrontEnd fe = null;
	
	final float OverloadThreshold; //guessing
	final float LowLoadThreshold;
	final float HighLoadThreshold;
	
	ArrayList<Message> serverResponses = new ArrayList<Message>();
	
	public LoadBalancer(InputParameters simulatorParameters, FrontEnd frontEnd) {
		simParams = simulatorParameters;
		fe = frontEnd;
		OverloadThreshold = (float)simulatorParameters.getOverLoadThreshold();
		LowLoadThreshold = (float)simulatorParameters.getLowLoadThreshold();
		HighLoadThreshold = (float)simulatorParameters.getHighLoadThreshold();
	}
	
	public void runAlgorithm(){
		//this will do just about everything.
		balanceLoad(true);
	}
	
	public void offloadServerResponded(Message respMsg) {
		if(respMsg.type == Message.Type.REQUEST_LOAD_ACK)
		{
			serverResponses.add(respMsg);
			if(serverResponses.size() < simParams.getFrontendCount() - 1)
				return; //wait on all responses
			balanceLoad(false);
		}
	}
	
	void balanceLoad(boolean isLocal){
		boolean needToPoll = false; //set to true if we should ask all other FEs for Load/File status
		for(WorkerServer ws : getOverloadedWorkers()) {
			for(int i=0; i<ws.requests.size(); i++) {
				Request req = ws.requests.poll();
				ws.requests.add(req); //only way to iterate thru a queue
				if(!isLocal)
					System.out.println("finding remote helper");
				Server relief = getReliefServer(ws, req, isLocal);
				if(relief == null){ 
					if(isLocal) 
						needToPoll = true;
					continue;
				}
				if(isLocal)
					relief.addRequest(req);
				else
					fe.sendMessage(createRequestPassingMessage((FrontEnd)relief, req));
				ws.requests.remove(req);
				System.out.println(String.format("LB: moved request %s from server %s to %s",
						req.toString(), ws.getUniqueIdentifier(), relief.getUniqueIdentifier()));
				if(ws.getLoad() < LowLoadThreshold) {
					needToPoll = false;
					break; //offloaded enough, trying to prevent thrashing?
				}
			}
		}		
		if(!isLocal)
			serverResponses.clear();
		if(needToPoll) {
			Message[] m = createLoadMessages();
			for(int i=0; i<m.length; i++)
				fe.sendMessage(m[i]);
		}
	}
	
	WorkerServer[] getOverloadedWorkers() {
		ArrayList<WorkerServer> ws = new ArrayList<WorkerServer>();
		for(int i=0; i<fe.getWorkerCount(); i++){
			WorkerServer w = fe.getWorker(i);
			if(w.getLoad() >= OverloadThreshold)
				ws.add(w);
		}
		return ws.toArray(new WorkerServer[ws.size()]);
	}
	

	Server getReliefServer(WorkerServer offloading, Request pendingXferReq, boolean isLocal) {
		Server best = null;
		float bestLoad = Float.MAX_VALUE;
		if(isLocal)
		{
			for(int i=0; i<fe.getWorkerCount(); i++){
				WorkerServer w = fe.getWorker(i);
				if(w.equals(offloading)) 
					continue;
				if(canWorkerSatisfyRequest(w, pendingXferReq) && w.getLoad() < bestLoad) {
					best = w;
					bestLoad = w.getLoad();
				}				
			}
		}
		else
		{
			FrontEnd cur;
			for(Integer ix : fe.frontEnds.keySet()) {
				if((cur = fe.frontEnds.get(ix)) == fe) continue;
				for(int i=0; i<cur.getWorkerCount(); i++) {
					WorkerServer w = cur.getWorker(i);
					if(canWorkerSatisfyRequest(w, pendingXferReq) && w.getLoad() < bestLoad) {
						best = cur;
						bestLoad = w.getLoad();
					}									
				}
			}
		}
		if(offloading.getLoad() < bestLoad)
			return null;
		return best;
	}
	
	boolean canWorkerSatisfyRequest(WorkerServer w, Request r) {
		return w.hasFileBlock(r.fileId, r.currentBlockId);
	}
	
	Message[] createLoadMessages() {
		Message[] m = new Message[simParams.getFrontendCount()-1];
		Iterator<Integer> it = fe.frontEnds.keySet().iterator();
		for(int i=0; i<m.length; i++) {
			Integer ix;
			do{ ix = it.next(); }while(ix.intValue() == fe.getId());
			m[i] = new Message(Message.Type.REQUEST_LOAD, fe,fe.frontEnds.get(ix));
		}
		return m;
	}
	
	Message createRequestPassingMessage(FrontEnd dest, Request r) {
		Message m = new Message(Message.Type.RECEIVED_REQUEST, fe, dest);
		m.requests.add(r);
		return m;
	}
}
